/* OBS.: Templates cannot be part of the framework dll, because they only
   generate code when they are specialized. Therefore, to be used as a template
   (and not as a set of pre-defined specializations), it must be
   used directly from this header file (which should be included in the client
   application code), instead from the framework lib/dll.
*/
#ifndef VECTORTEMPLATE_H
#define VECTORTEMPLATE_H

#pragma once

#include <stdexcept>
#include <iostream>
#include <cmath>

/**
 * This class defines a template for a multi-dimensional vector structure. In
 * order to use it, the dimension and the type of the elements must be given.
 * Some common cases are already provided as the defined types Vector2i,
 * Vector2f, Vector2d, Vector3i, Vector3f, and Vector3d.
 *
 * The methods in this template class do not provide boundary check. The user
 * must make sure that indices are inside the bounds of the specified vector
 * dimension when using it. This provides better performance and assumes that
 * this kind of logic mistake should be rare.
 *
 * This template was based on the example given to the students by TAs Torin
 * Taerum and Fabricio Anastacio in the CPSC453 Introduction to Computer
 * Graphics course at the University of Calgary, Canada, in the Fall 2006 term.
 *
 * @author Fabricio Anastacio - fabriciocfa@yahoo.com
 * @since June 27, 2007
 */
template<unsigned int N, class T> class VectorTemplate
{
	// Attributes
private:
	/** The dimensional data of this vector. */
	T data[N];

	// Methods
public:
	/**
	 * Constructs a vector of the given dimension and type initialized with 0.
	 */
	VectorTemplate<N, T>(void) {
		for (unsigned int i = 0; i < N; i++) data[i] = 0;
	};
	/**
	 * Constructor for copying a vector of the given dimension and type.
	 *
	 * @param v the vector to be copied to this one.
	 */
	VectorTemplate<N, T>(const VectorTemplate<N, T>& v) {
		for (unsigned int i = 0; i < N; i++) data[i] = v[i];
	};
	/**
	 * Destroys this vector.
	 */
	~VectorTemplate<N, T>(void) {
		//delete [] data;
	};

	// OVERLOADED OPERATORS
	/**
	 * Vector assignment.
	 *
	 * @v the vector to be assigned to this one.
	 */
	VectorTemplate<N, T>& operator=(const VectorTemplate<N, T>& v) {
		for (unsigned int i = 0; i < N; i++) data[i] = v[i];
		return *this;
	};
	/**
	 * Vector equal comparison.
	 *
	 * @v the vector to be assigned to this one.
	 */
	bool operator==(const VectorTemplate<N, T>& v) const {
		bool r = true;
		for (unsigned int i = 0; i < N; i++) r = r && (data[i] == v[i]);
		return r;
	};
	/**
	 * Array-like access to the elements.
	 *
	 * @param i the index of the element in the data array.
	 */
	inline T& operator[](unsigned int i) { return data[i]; };
	/**
	 * Array-like access to the elements (const version).
	 *
	 * @param i the index of the element in the data array.
	 */
	inline const T& operator[](unsigned int i) const { return data[i]; };

	// ARITHMETICAL OPERATORS
	/**
	 * Adds the vector v to the current vector.
	 *
	 * @param v the vector to be added to this one.
	 */
	inline VectorTemplate<N, T>& operator+=(const VectorTemplate<N, T>& v) {
		for(unsigned int i = 0; i < N; i++) data[i] += v[i];
		return *this;
	};
	/**
	 * Subtracts the vector v to the current vector.
	 *
	 * @param v the vector to be subtracted from this one.
	 */
	inline VectorTemplate<N, T>& operator-=(const VectorTemplate<N, T>& v) {
		for(unsigned int i = 0; i < N; i++) data[i] -= v[i];
		return *this;
	};
	/**
	 * Multiplies the value of the scalar s by the current vector.
	 *
	 * @param s the scalar value to be multipled by this vector.
	 */
	inline VectorTemplate<N, T>& operator*=(T s) {
		for(unsigned int i = 0; i < N; i++) data[i] *= s;
		return *this;
	};
	/**
	 * Divides the value of the scalar s by the current vector.
	 *
	 * @param s the scalar value to be divided by this vector.
	 */
	inline VectorTemplate<N, T>& operator/=(T s) {
		for(unsigned int i = 0; i < N; i++) data[i] /= s;
		return *this;
	};
	/**
	 * Negates this vector.
	 */
	inline VectorTemplate<N, T> operator-() {
		VectorTemplate<N, T> r;
		for(unsigned int i = 0; i < N; i++) r[i] = -data[i];
		return r;
	};
	/**
	 * Returns the result of adding the vector v to the current vector.
	 *
	 * @param v the vector to be added to this one.
	 * @return the resulting of adding this vector to v.
	 */
	inline VectorTemplate<N, T> operator+(const VectorTemplate<N, T>& v) const {
		VectorTemplate<N, T> r = *this;
		r += v;
		return r;
	};
	/**
	 * Returns the result of subtracting the vector v from the current vector.
	 *
	 * @param v the vector to be added to this one.
	 * @return the result of subtracting this vector from v.
	 */
	inline VectorTemplate<N, T> operator-(const VectorTemplate<N, T>& v) const {
		VectorTemplate<N, T> r = *this;
		r -= v;
		return r;
	};
	/**
	 * Returns the result of multiplying this vector by the scalar s.
	 *
	 * @param s the scalar value to be multiplied by this vector.
	 * @return the result of multiplying this vector by s.
	 */
	inline VectorTemplate<N, T> operator*(T s) const {
		VectorTemplate<N, T> r = *this;
		r *= s;
		return r;
	};
	/**
	 * Returns the result of dividing this vector by the scalar s.
	 *
	 * @param s the scalar value to be divided by this vector.
	 * @return the result of dividing this vector by s.
	 */
	inline VectorTemplate<N, T> operator/(T s) const {
		VectorTemplate<N, T> r = *this;
		r /= s;
		return r;
	};


	// VECTOR OPERATIONS
	/**
	 * Returns the dot product between this vector and v.
	 *
	 * @param v the vector to be dot-producted with this one.
	 * @return the dot product between this vector and v.
	 */
	inline double dot(const VectorTemplate<N, T>& v) const {
		double r = 0.0;
		for (unsigned int i = 0; i < N; i++) r += (data[i] * v[i]);
		return r;
	};
	/**
	 * Returns the length (a.k.a., norm, magnitude) of this vector.
	 *
	 * @return the length of this vector.
	 */
	inline double length() const {
		double r = 0.0;
		for (unsigned int i = 0; i < N; i++) r += (data[i] * data[i]);
		return sqrt(r);
	};
	/**
	 * Normalizes this vector.
	 */
	inline VectorTemplate<N, T>& normalize() {
		double norm = length();
		if (norm != 0) for (unsigned int i = 0; i < N; i++) data[i] /= (T) norm;
		return *this;
	};

	/**
	 * Calculates the angle in radians between this vector and v. This
	 * operation is only implemented for two dimensional vectors. If they have
	 * different dimensions a std::logic_error exception is thrown.
	 *
	 * @param v the vector whose angle with this one will be calculated.
	 * @return the angle between this vector and the given one.
	 * @throw std::logic_error exception if the vectors are not 2D.
	 */
	double calculateAngleWith(const VectorTemplate<N, T>& v) {
		// Checking for correct dimension
		if (N != 2) throw std::logic_error(
			"Trying to calculate the angle between vectors with dimension different than 2!");
		VectorTemplate<N, T> v1 = *this;
		VectorTemplate<N, T> v2 = v;
		v1.normalize();
		v2.normalize();
		return acos(v1.dot(v2));
	};

	/**
	 * Calculates the signed angle in radians between this vector and v. In
	 * other words, it returns the angle that this vector must be rotated by so
	 * that it points in the same direction as v. The returned angle is inside
	 * the (-PI, PI) interval. This operation is only implemented for two
	 * dimensional vectors. If they have different dimensions a
	 * std::logic_error exception is thrown.
	 *
	 * @param v the vector whose signed angle with this one will be calculated.
	 * @return the signed angle between this vector and the given one.
	 * @throw std::logic_error exception if the vectors are not 2D.
	 */
	double calculateSignedAngleWith(const VectorTemplate<N, T>& v) {
		// Checking for correct dimension
		if (N != 2) throw std::logic_error(
			"Trying to calculate the angle between vectors with dimension different than 2!");
		return atan2(v[1], v[0]) - atan2(data[1], data[0]);
	};

	/**
	 * Returns a vector with the cross product of this vector with v. This
	 * operation only makes sense if the involved vectors have three
	 * dimensions. If they have different dimensions a std::logic_error
	 * exception is thrown.
	 *
	 * @param v the vector to be cross-producted with this one (this X v).
	 * @return the vector resulting from the cross product of this vector and v.
	 * @throw std::logic_error exception if the vectors are not 3D.
	 */
	inline VectorTemplate<N, T> cross(const VectorTemplate<N, T>& v) {
		// Checking for correct dimension
		if (N != 3) throw std::logic_error(
			"Trying to apply cross-product to a vector with dimension different than 3!");
		// Calculating the dot product
		VectorTemplate<3, T> r;
		r[0] = data[1] * v[2] - data[2] * v[1];
		r[1] = data[2] * v[0] - data[0] * v[2];
		r[2] = data[0] * v[1] - data[1] * v[0];
		return r;
	};
	

	// DEBUGGING METHODS
	void print() {
		std::cout << "(";
		for (unsigned int i = 0; i < N; i++) std::cout << data[i] << (i == N - 1 ? ")\n" : ", ");
	}
};

// Predefined classes for friendlier use
typedef VectorTemplate<2, int> Vector2i;
typedef VectorTemplate<3, int> Vector3i;
typedef VectorTemplate<2, float> Vector2f;
typedef VectorTemplate<3, float> Vector3f;
typedef VectorTemplate<2, double> Vector2d;
typedef VectorTemplate<3, double> Vector3d;


#endif // VECTORTEMPLATE_H
